
#import shutil
import re
#import sys
import os



def search_path(root, mm, n=0):
    out = []
    if isinstance(mm, str):
        mm = mm.split('/')
    if n >= len(mm):
        out.append(root)
        return out
    if not os.path.isdir(root):
        print('错误：{}'.format("路径非文件夹  "), root)
        return []
    try:
        for file in os.listdir(root):
            if re.fullmatch(mm[n], file):
                f = os.path.join(root, file)
                if os.path.isdir(f):
                    out += search_path(f, mm, n+1)
    except:
        pass
    return out


def gen_deffile(exports, out):
    xx = '''LIBRARY
EXPORTS
'''
    for i in range(len(exports)):
        xx += '%s @%d\n' % (exports[i], i+1)

    open(out, 'w').write(xx)
    return xx


class Cmd:
    '''
    cmc
    '''

    def __init__(self):
        self.cmd = ''

    def displayEmployee(self):
        pass


def is_new_then(srcs, out):
    if os.path.exists(out):
        out_mtime = os.path.getmtime(out)
        for src in srcs:
            if not os.path.exists(out):
                return True

            src_mtime = os.path.getmtime(src)
            if src_mtime > out_mtime:
                return True
    else:
        return True

    return False


def remove(fname):
    if os.path.exists(fname):  # 如果文件存在
        os.remove(fname)
        # os.unlink(path)
    else:
        print('no such file: %s' % fname)  # 则返回文件不存在


def getvalue(dic, name, defvalue=''):
    return dic[name] if name in dic else defvalue


def mkdir(path):
    try:
        os.makedirs(path)
    # Python >2.5 (except OSError, exc: for Python <2.5)
    except OSError as exc:
        pass


def run_bin(bin_path, cmd):
    bin_str = ';'.join(map(lambda x: '%s' % x, bin_path))
    if isinstance(cmd, str):
        cmd = [cmd]
    xx = ['set PATH=%s;%%PATH%%' % bin_str] + cmd
    xx = ' & '.join(xx)
    print(xx)
    os.system(xx)
    return xx


class CppMake:
    def __init__(self, cc, config={}):
        self.cc = cc
        self.plat = getvalue(config, 'plat', 'x64')
        self.flags = getvalue(config, 'flags')
        self.link_flags = getvalue(config, 'link_flags')
        self.deffile = getvalue(config, 'deffile')
        self.work_path = getvalue(config, 'D:/bin/dll/obj')
        self.debug = getvalue(config, 'debug', False)
        self.force_compile = getvalue(config, 'force_compile', False)
        self.inc = getvalue(config, 'inc', [])
        self.libp = getvalue(config, 'libp', [])
        self.exports = getvalue(config, 'exports', [])
        self.openmp = getvalue(config, 'openmp', False)
        self.sse = getvalue(config, 'sse', True)
        # self.libs = getvalue(config, 'libs', [])
        pass

    def set_cc(self, cc):
        self.cc = cc

    def set_exports(self, exports):
        if isinstance(exports, str):
            exports = exports.split(' ')
        self.exports = exports

    def run(self, xx):
        run_bin(self.cc.bin_path[self.plat], xx)

    def make(self, srcs, libs, outs, tags=''):
        self.force_compile = '!' in tags
        self.plat = 'x86' if 'x86' in tags else 'x64'
        self.debug = True if 'debug' in tags else False
        self.vis = True if '@' in tags else False
        self.openmp = True if 'openmp' in tags else False
        if isinstance(outs, str):
            out1 = {}
            if '.exe' in outs:
                out1['exe'] = outs
            if '.dll' in outs:
                out1['dll'] = outs
            if '.lib' in outs:
                out1['lib'] = outs
            if '.a' in outs:
                out1['lib'] = outs
            outs = out1

        cc = self.cc
        conf = self
        xx = {}
        objs = []
        d = 'Debug' if conf.debug else 'Release'
        work_path = f'{self.work_path}/{self.cc.__class__.__name__}'
        work_plat = f'{work_path}/{self.plat}'
        work_plat_d = f'{work_plat}/{d}'
        mkdir(work_plat_d)
        for i in range(len(srcs)):
            src = srcs[i]
            _, filenameext = os.path.split(src)
            fn, _ = os.path.splitext(filenameext)
            # name = '%s%s%s_%s' % (fn, conf.plat, d, cc.name)
            #name = '%s%s%s' % (fn, conf.plat, d)
            obj = f'{work_plat_d}/{fn}.obj'
            objs.append(obj)
            force_compile = conf.force_compile or i==0
            if src.endswith('!'):
                force_compile = True
                src = src[:-1]

            if force_compile or is_new_then([src], obj):
                xx[obj] = cc.compile(conf, [src], obj)

        # if is_new_then(objs+[deffile], out):
        for tag in outs.keys():
            out = outs[tag]
            out_path, _ = os.path.split(out)
            mkdir(out_path)
            xx[out] = cc.link(conf, objs, libs, out, tag)
            print('-->', out)
        return xx

    def set_plat(self, plat):
        self.plat = plat

    def set_work_path(self, work_path):
        self.work_path = work_path

    def set_inc(self, inc):
        self.inc = inc

    def set_libp(self, libp):
        self.libp = libp


# R = 'C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/VC/Tools/MSVC/14.26.28801'
class MsvcCompile:
    def __init__(self):
        vs_root = 'C:/Program Files (x86)/Microsoft Visual Studio'
        kits_root = 'C:/Program Files (x86)/Windows Kits'

        includes = []
        includes += search_path(
            vs_root, '[0-9.]+/Enterprise/VC/Tools/MSVC/[0-9.]+/include')
        includes += search_path(
            kits_root, '[0-9]+/Include/[0-9.]+/(?:ucrt|um|shared)')

        self.inc_path = includes
        self.lib_path = {}
        self.bin_path = {}
        self.s_plat = ['x64', 'x86']
        self.name = 'vs'
        name = search_path(vs_root, '[0-9.]+')
        if len(name) > 0:
            _, name = os.path.split(name[0])
            self.name += name

        for plat in self.s_plat:
            libs = []
            libs += search_path(
                vs_root, '[0-9.]+/Enterprise/VC/Tools/MSVC/[0-9.]+/lib/%s' % plat)
            libs += search_path(
                kits_root, '[0-9]+/Lib/[0-9.]+/(?:ucrt|um|shared)/%s' % plat)

            vs_bin = search_path(
                vs_root, '[0-9.]+/Enterprise/VC/Tools/MSVC/[0-9.]+/bin/Hostx64/%s' % (plat))

            self.bin_path[plat] = vs_bin
            self.lib_path[plat] = libs

    def compile(self, conf, srcs, out, inc=[]):
        if os.path.exists(out):
            remove(out)
        src = ' '.join(srcs)
        inc1 = conf.inc + self.inc_path
        inc_str = ' '.join(map(lambda x: '"/I%s"' % x, inc1))
        cmd = ['cl.exe /nologo /c', src, conf.flags,
               '/bigobj',
               '/Fo:%s' % out,
               '/D "_CRT_NONSTDC_NO_DEPRECATE"',
               '/D "_CRT_SECURE_NO_WARNINGS"',
               inc_str
               ]
        if conf.debug:
            cmd += ['/JMC /permissive- /GS /W0 /Zc:wchar_t /Gm- /Od /sdl',
                    '/Zc:forScope /RTC1 /Gd /MDd /FC',
                    '/Od',
                    # '/ZI',
                    # '/GZ',
                    '/D "_DEBUG"',
                    ]
        else:
            cmd += ['/permissive- '
                    '/Gd /Oi /MD /FC',
                    '/GS /GL /W0 /Gy',
                    '/WX- /Zc:forScope',
                    '/Gm- /O2 /sdl',
                    #'/EHsc /diagnostics:column',
                    # '/Zi',
                    '/fp:precise',
                    '/D "_WIN32"',
                    '/D "_CRT_SECURE_NO_WARNINGS"',
                    '/D "NDEBUG"'
                ]

        '''
        /permissive- /GS /GL /W0 /Gy /Zc:wchar_t /Zi /Gm- /O2 /sdl /Fd"x64\Release\vc142.pdb"
        /Zc:inline /fp:precise /D "_CRT_SECURE_NO_WARNINGS" /D "NDEBUG" /D "_CONSOLE"
        /errorReport:prompt /WX- /Zc:forScope /Gd /Oi /MD /FC /Fa"x64\Release\"
        /EHsc /nologo /Fo"x64\Release\" /Fp"x64\Release\testc.pch" /diagnostics:column 
        '''
        if conf.openmp:
            cmd += ['/openmp']
        cmd = ' '.join(cmd)
        if conf.vis:
            print(cmd)
        xx = [cmd]
        run_bin(self.bin_path[conf.plat], xx)
        return cmd

    def link(self, conf, objs, libs, out, tag):
        objs = ' '.join(objs)
        cmd = ''
        if os.path.exists(out):
            remove(out)
        if 'lib' in tag:
            cmd = 'lib %s /OUT:%s /NOLOGO /LTCG' % (objs, out)
        else:
            cmd = ['link /nologo', objs, '/out:%s' % out, conf.link_flags]
            if tag == 'dll':
                cmd += [' -DLL']
                if len(conf.exports) > 0:
                    _, out_fnext = os.path.split(out)
                    out_fn, _ = os.path.splitext(out_fnext)
                    deffile_fn = os.path.join(
                        conf.work_path, '%s.def' % out_fn)
                    gen_deffile(conf.exports, deffile_fn)
                    cmd += ['/def:%s' % deffile_fn]

            '''
            /OUT:"D:\code\testc\x64\Release\testc.exe" /MANIFEST /LTCG:incremental 
            /NXCOMPAT /PDB:"D:\code\testc\x64\Release\testc.pdb /DYNAMICBASE "
            "kernel32.lib" "user32.lib" "gdi32.lib" "winspool.lib" "comdlg32.lib" "advapi32.lib" "shell32.lib" "ole32.lib" "oleaut32.lib" "uuid.lib" "odbc32.lib" "odbccp32.lib" 
            /DEBUG /MACHINE:X64 /OPT:REF /INCREMENTAL:NO /PGD:"D:\code\testc\x64\Release\testc.pgd" 
            /SUBSYSTEM:CONSOLE /MANIFESTUAC:"level='asInvoker' uiAccess='false'" 
            /ManifestFile:"x64\Release\testc.exe.intermediate.manifest" /OPT:ICF /ERRORREPORT:PROMPT /NOLOGO /TLBID:1 
            '''
            libpath1 = conf.libp + self.lib_path[conf.plat]
            cmd += [' '.join(map(lambda x: '"/LIBPATH:%s"' % x, libpath1))]
            cmd += [' '.join(libs)]
            cmd = ' '.join(cmd)

        # xx = ' & '.join(xx)
        # print(xx)
        if conf.vis:
            print(cmd)
        xx = [cmd]
        run_bin(self.bin_path[conf.plat], xx)
        return cmd


class MingwCompile:
    def __init__(self):
        roots = ['D:/LLVM/mingw64', 'C:/Program Files/Git/mingw64']
        includes = []
        bin_path = []
        for root in roots:
            bin_path += search_path(root, 'bin')
            includes += search_path(root,
                                    'lib/gcc/.+mingw.*/[0-9.]+/include')
            if len(bin_path) > 0:
                break

        self.inc_path = includes
        self.bin_path = {}
        self.lib_path = {}
        self.bin_path['x64'] = []
        self.lib_path['x64'] = []
        self.s_plat = ['x64']
        self.name = 'mingw'
        self.bin_path['x64'] = bin_path

    def compile(self, conf, srcs, out, inc=[]):
        if os.path.exists(out):
            remove(out)
        src = ' '.join(srcs)
        inc1 = conf.inc + self.inc_path
        inc_str = ' '.join(map(lambda x: '-I "%s"' % x, inc1))
        cmd = ['gcc.exe -c', src, conf.flags, '-o %s' % out, inc_str]
        if conf.debug:
            cmd += ['-g']
        else:
            cmd += ['-O3']

        if conf.sse:
            cmd += ['-msse4.1']
            
        cmd = ' '.join(cmd)
        if conf.vis:
            print(cmd)
        xx = [cmd]
        run_bin(self.bin_path[conf.plat], xx)
        return cmd

    def link(self, conf, objs, libs, out, tag):
        objs = ' '.join(objs)
        cmd = ''
        if os.path.exists(out):
            remove(out)
        if 'lib' in tag:
            cmd = 'ar -rcs %s %s' % (out, objs)
        else:
            cmd = ['gcc', objs, '-o %s' % out, conf.link_flags]
            if tag == 'dll':
                cmd += ['-shared']
                if len(conf.deffile) > 0:
                    # gen_deffile(defs, deffile)
                    cmd += ['/def:%s' % (conf.deffile)]

            cmd += ['-lstdc++']
            syslibs = 'OleAut32 Shell32 ws2_32 gdi32 ole32 comctl32 strmiids d3d9'.split(' ')
            cmd += [f'-l {x}' for x in syslibs]
            
            libpath1 = conf.libp + self.lib_path[conf.plat]
            cmd += [' '.join(map(lambda x: '-L "%s"' % x, libpath1))]
            cmd += [' '.join(libs)]
            cmd = ' '.join(cmd)

        # xx = ' & '.join(xx)
        if conf.vis:
            print(cmd)
        xx = [cmd]
        run_bin(self.bin_path[conf.plat], xx)
        return cmd


def test_mkdll():
    # upload xls file SVN
    plat = 'x86'
    plat = 'x64'
    out_dir = 'D:/bin/dll/%s' % plat
    mkdir(out_dir)
    xx = 'libadaboost'
    src = '%s.cpp' % xx
    out = '%s/%s.dll' % (out_dir, xx)
    deffile = '%s.def' % (xx)
    exp = ['detect_object',
           'load_haar_cascade',
           'free_cascade',
           'add_int',
           'add_float',
           'add_str',
           'szSecTest_double',
           'show_matrix',
           'mysum2_uint8',
           'create_struct',
           'delete_struct',
           'show_struct',
           'mysum',
           'mysum2_double']

    if is_new_then(['mkdll.py'], deffile):
        gen_deffile(exp, deffile)

    mkdll(compiler, linker, [src], deffile, out, out_dir)
    return out_dir

def test_mkdll_adaboost():
    cc = CppMake(MsvcCompile())
    p = 'D:/bin/dll'
    inc = 'D:/code/git/ywlydd/cstd/include'
    cc.set_inc([inc])
    cc.set_work_path('%s/obj' % p)
    cc.set_libp([p])
    cc.set_exports('detect_object load_haar_cascade free_cascade')
    tags = 'x64 openmp !'
    cc.make(['%s/adaboost/libadaboost.cpp'%inc], [], '%s/libadaboost.dll' % p, tags)
    cc.make(['%s/adaboost/test_adaboost.cpp'%inc], [], '%s/test_adaboost.exe' % p, tags)
    #cc.run('dumpbin /exports ' + '%s/libtest.dll' % p)


if __name__ == "__main__":
    # os.chdir('build')
    # test_mkdll()
    test_mkdll_adaboost()
